

########################## MODEL ######################################
class Model(object):
    import database
    DB = database.Database('puffin')
    #OLD CODE: sql = sqlite3.connect('puffin') #connects to database. creates it if it does not exist.
    #OLD CODE: queryCurs = sql.cursor() #cursor allows you to run queries.
    
    def serializeDate(date_string):
        #The DateString value will be a string "(Jan, 1, 2001)". This function removes the parentheses from the string and serializes the number values
        date_string = date_string.replace('(','')
        date_string = date_string.replace(')','')#http://www.daniweb.com/software-development/python/threads/71947
        date_string = date_string.split(', ') #http://www.astro.ufl.edu/~warner/prog/python.html
        return date_string

    def dateHelp(dictionary):
        #Even though dateHelp appears to accept a dictionary value, it actually receives a string value from the addEvent() and modifyEvent(). serializeDate() will turn the string into a dictionary.
        date_string = Model.serializeDate(dictionary["date"]) 
        dictionary.update({"year":date_string[0]})
        dictionary["month"]=date_string[1]
        dictionary["day"]=date_string[2]
        return dictionary

    def addEvent(dictionary):
        #addEvent accepts a dictionary value
        #My database table has a separate column for day, month, and year. It also has a convenience column for all three values together.
        #Model.dateHelp() will take a string "(Jan, 1, 2001)", and transform it. This function removes the parentheses from the string for the convenience date column, and it serializes the date values for the d - m - y columns
        dictionary = Model.dateHelp(dictionary)
        Model.DB.insert("calendar", dictionary)

    def modifyEvent(dictionary):
        #modifyEvent accepts a dictionary value
        #My database table has a separate column for day, month, and year. It also has a convenience column for all three values together.
        #Model.dateHelp() will take a string "(Jan, 1, 2001)", and transform it. This function removes the parentheses from the string for the convenience date column, and it serializes the date values for the d - m - y columns
        dictionary = Model.dateHelp(dictionary)
        #(table_name, key_value, primary_key)
        Model.DB.update("calendar",dictionary,"event_id")

    def deleteEvent (event_id):
        #(table_name, key, value)
        Model.DB.delete("calendar","event_id",event_id)

    def getEventsHeadings(date):
        #get data from DB
        #OLD CODE: data = Model.queryCurs.execute("SELECT event_id, heading FROM calendar WHERE date='"+date+"'")
        #(table_name, parameters, requested_fields=*)
        data = Model.DB.select("calendar","date='"+date+"'",["event_id","heading"])
        #transform SQL object into a list
        ldata = []
        for row in data:
            ldata.append(row)
        return ldata

    def getEventsDetails(event_id):
        #OLD CODE: k=Model.queryCurs.execute("SELECT * FROM calendar WHERE event_id='"+event_id+"'")
        k=Model.DB.select("calendar","event_id='"+event_id+"'")
        for row in k:
            return row
    
########################## VIEW ######################################            
import tkinter
import tkinter.constants as TKC
import tkinter.ttk as ttk
import calendarHelp
CH = calendarHelp.CalendarHelp()
class View:
    def __init__(self):
        global root
        root = tkinter.Tk()

class GlobalMenu(object):
    def __init__(self):
        self.window_frame = tkinter.Toplevel(root)
        #window_frame.pack()
        self.menu_frame = tkinter.Frame(self.window_frame)
        self.menu_frame.pack(side=TKC.TOP)
        self.label = tkinter.Label(self.menu_frame, text="a")
        self.label.pack(side=TKC.TOP)
        self.menu_bar = tkinter.Menu(self.label)
        #DropDown = menu_bar.add_cascade(label="Action")
        #DropDown.pack()

class CalendarView: #This creates the main screen when you are looking at the calendar
    Weekdays = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"] #This is a little helper
    def __init__(self):
        self.Cal = tkinter.Frame(root);self.Cal.pack() #Create the top level frame for the Calendar View
        self.week_counter = 0 #Contains state information for which week is being displayed. see showEventsOnCalendar() for more information.

        ######### STATE INFORMATION ###############
        week_counter = self.week_counter #Remember that week_counter was initialized in the __init__. See the comments below for how week_counter works.
        #positive values will display weeks in the future
        #negative values will display weeks in the past
        #if week_counter == 0: it will display the present
        #the value is always changed in increments of 2, because we display 2 weeks at a time. I think it will give me more control in the future.
        #see changeWeek() for more documentation
        

        ################ NAVIGATION BUTTONS #################
        #navigation ahead in time or backwards in time
        navigation_frame = tkinter.Frame(self.Cal); navigation_frame.pack() #Create the frame to hold the widgets
        goToPast = lambda x=self: self.showDifferentWeek("past") #defining the command to view past events
        last_week = tkinter.Button(self.Cal, text="Previous", command=goToPast); last_week.pack(side=TKC.LEFT, anchor=TKC.N) #creating the widget
        goToPresent = lambda x=self: self.showDifferentWeek("present") #defining the command to return to view of present week
        present = tkinter.Button(self.Cal, text="Current", command=goToPresent); present.pack(side=TKC.LEFT, anchor=TKC.N) #creating the widget
        goToFuture = lambda x=self: self.showDifferentWeek("future") #defining the command to view future events
        next_week = tkinter.Button(self.Cal, text="Upcoming", command=goToFuture); next_week.pack(side=TKC.LEFT, anchor=TKC.N) #creating the widget

        #Creating the M,T,W,R,F,S,S at the top of the calendar
        self.calendar_frame = tkinter.Frame(self.Cal)
        self.calendar_frame.pack()
        for i in range(7): #Create the Days as headers on top of the calendar
            #Create a frame with the text of the Frame corresponding to the day of the week. Example: 0 = Monday, 1=Tuesday, 2=Wednesday, etc. 
            tkinter.Label(self.calendar_frame, text=CalendarView.Weekdays[i]).grid(row=0, column=i, padx=15, pady=13)

        initial_value = week_counter*7 #where week_counter=0: the view of the present week.

        #This is for the CalendarView.showDifferentWeek() to work. The list will hold all the objects that must be updated to show different weeks.
        self.events_row = []
        
        for i in range(initial_value, initial_value+7):  #Creates the first calendar row of dates and events
            var = CalendarView.WeekdayEvents(self.calendar_frame, i)
            self.events_row.append(var)
        
        for i in range(initial_value+7,initial_value+14):  #Creates the second calendar row of dates and events
            var = CalendarView.WeekdayEvents(self.calendar_frame, i, 2)
            self.events_row.append(var)

    def showDifferentWeek(self, switch):
        if switch =="future":
            n = 14 #moves all days in the calendar grid ahead by 2 weeks
            self.week_counter = self.week_counter+2 #Increment the counter in a positive direction
        elif switch =="past":
            n = -14 #moves all days in the calendar grid behind by 2 weeks
            self.week_counter = self.week_counter-2 #Increment the counter in a negative direction
        elif switch =="present":
            n = self.week_counter * -7  #we only multiply the week_counter by 7 because the week_counter increments by 2. If week_counter incremented by one, we should multiply by 14 (negative, of course).
                                        #If pressed forward by 2 week, the week_counter would equal 4. All grid_objects would be 28 days ahead of the dates displayed by those grid_objects on present view
            self.week_counter = 0 # Reset the Counter when we return to view the present weeks.
        for i in self.events_row: #The objects in the events_row have an update() method that will handle destroying old memory and updating with new information.
            i.update(n)
        #One of the advantages of updating the information calendar grid by calendar grid, instead of simply asking the entire calendar to refresh, is this new method minimizes black flashing. tkinter performance in this regard is not ideal.
   
    class WeekdayEvents:
        def __init__(self, parent, i, _row=1):
            self.i = i #This allows the update() function to work. It specifies which day the event occurs on
            self.row = _row #This allows the update() function to work. It specifics which row the event occurs in.
            self.parent = [parent] #This allows the update() function to work. It holds a reference to the parent object
            self.registry = [] #This holds all sub-objects of WeekdayView so that old objects can be destroyed during the update() function
            date = CH.getCalendarDate(i)
            #Retrieves a tuple of the date (YYYY,MM,DD)
            readable_date = CH.getCalendarDateReadable(date) #Converts date tuple (2012, 01, 01) into string "Jan 01"
            weekday = tkinter.Frame(parent, height=13, width=18) #Each day is wrapped in a frame
            self.register(weekday) #Only the weekday frame needs to be registered, because when the frame is destroyed, all sub-objects will be destroyed as well.
            weekday.grid(row=_row, column = i%7, sticky=TKC.N)
            createEvent = lambda x = date: EventView.createEvent(x)
            button = tkinter.Button(weekday, text=readable_date, command=createEvent, relief=tkinter.GROOVE) #lambda: EventView.createEvent(date)) #This button shows the month and day. Clicking the button will allow user to create new event on that day.
            button.pack()
            event_info = CalendarController.getHeading(date)
            if event_info != None:
                for row in event_info:
                    event_id = str(row[0])
                    button_text = row[1]
                    modifyEvent = lambda y = event_id: EventView.modifyEvent(y)
                    eventButton = tkinter.Button(weekday, relief=tkinter.FLAT, text=button_text, command=modifyEvent) #Each event on a day is in a Label widget. clicking the Event allows the user to modify the event.
                    eventButton.pack(fill=tkinter.BOTH, expand=1)
            else:
                tkinter.Label(weekday).pack()
            Observer.register([self,str(date)])
        def update(self, i=0):
            self.registry[0].destroy() #remove old data from memory. One might think this is removing the object from the Observer list, but instead, it is destroying the old frame.
            #self.parent is a list containing a reference to the parent frame. [0] points to the only object in the list
            #self.i specifies which day of the week that the event belongs to.
            #self.row specifies which row (1st or 2nd) that the event belongs to.
            self.__init__(self.parent[0], self.i+i, self.row)
        def register(self, subobject):
            self.registry.append(subobject)
        def event_day(self, ifgj):
            pass


class EventView:
    def __init__(self):
        self.dialog = tkinter.Toplevel(root)
        self.dialog.title("Event Details")
        escape = lambda e: self.dialog.destroy()#Pressing escape will exit the 
        self.dialog.bind("<Escape>", escape)
        self.window_frame = ttk.Frame(self.dialog)
        self.window_frame.pack()

        
    def modifyEvent(event_id):
        #Most of the stuff in here is tkinter boilerplate
        total_columns=4 #This is the total number of columns in the grid layout
        self = EventView()
        event_details = CalendarController.getEventDetails(event_id)
        event_details = list(event_details)
        print (event_details) #For debugging DEBUGGING
        for i in range(len(event_details)): #Turns None values into empty string values. It first finds the number of variables contained in event_details (which can be an arbitrary number as I expand my program). For each variable in event_details, perform the followin check.
            if event_details[i] == None:    
                event_details[i] = ""
        date = event_details[1]
        self.content_frame = ttk.Frame(self.window_frame)
        self.content_frame.pack()
        self.dateLabel = ttk.Label(self.content_frame, text = "Date:"+date)
        self.dateLabel.pack()
        self.EVENTID = tkinter.StringVar()
        self.EVENTID.set(event_id)
        self.DATE = tkinter.StringVar()
        self.DATE.set(date)
        self.dateLabel.grid(row=0, column=0)
        self.headingLabel = ttk.Label(self.content_frame, text="Heading:")
        self.headingLabel.grid(row=0, column =2)
        self.headingEntry = ttk.Entry(self.content_frame)
        self.headingEntry.insert(0,event_details[5])
        self.headingEntry.grid(row=0, column=3)
        self.detailsLabel = ttk.Label(self.content_frame, text="Details")
        self.detailsLabel.grid(row=1, column = 0)
        self.detailsEntry = tkinter.Text(self.content_frame, height=4, width =50)
        self.detailsEntry.insert(1.0, event_details[6])
        self.detailsEntry.grid(row=2, column = 0, columnspan=total_columns)
        self.locationLabel = ttk.Label(self.content_frame, text="Location")
        self.locationLabel.grid(columnspan=total_columns)
        self.locationEntry = ttk.Entry(self.content_frame)
        self.locationEntry.insert(0, event_details[9])
        self.locationEntry.grid(columnspan=total_columns)
        self.invitees = ttk.Button(self.content_frame, text = "Invitees")
        self.invitees.grid(columnspan=total_columns)
        self.begin_timeLabel = ttk.Label(self.content_frame, text = "Begin Time")
        self.begin_timeLabel.grid(columnspan=total_columns)
        self.begin_time = ttk.Entry(self.content_frame)
        self.begin_time.insert(0, event_details[7])
        self.begin_time.grid(columnspan=total_columns)
        self.end_timeLabel = ttk.Label(self.content_frame, text = "End Time")
        self.end_timeLabel.grid(columnspan=total_columns)
        self.end_time = ttk.Entry(self.content_frame)
        self.end_time.insert(0, event_details[8])
        self.end_time.grid(columnspan=total_columns)
        deleteEvent = lambda b = self: CalendarController.deleteEvent(b)
        self.deleteButton = ttk.Button(self.content_frame, text = "Delete", command=deleteEvent)
        self.deleteButton.grid(columnspan=total_columns)
        modifyEvent = lambda y = self: CalendarController.modifyEvent(y)
        self.save = ttk.Button(self.content_frame, text="Save", command=modifyEvent)
        self.save.grid(columnspan=total_columns)
        
    def createEvent(date):
        self = EventView()
        self.content_frame = tkinter.Frame(self.window_frame)
        self.content_frame.pack()
        self.dateLabel = ttk.Label(self.content_frame, text="Date:"+str(date))
        self.DATE = tkinter.StringVar()
        self.DATE.set(date)
        self.dateLabel.grid(row=0, column=0)
        self.headingLabel = ttk.Label(self.content_frame, text="Heading:")
        self.headingLabel.grid(row=0, column =2)
        self.headingEntry = ttk.Entry(self.content_frame)
        self.headingEntry.grid(row=0, column=3)
        self.detailsLabel = ttk.Label(self.content_frame, text="Details")
        self.detailsLabel.grid(row=1, column = 0)
        self.detailsEntry = tkinter.Text(self.content_frame, height=4, width =50)
        self.detailsEntry.grid(row=2, column = 0, columnspan=4)
        self.saveEvent = lambda x = self: CalendarController.saveEvent(x)
        self.save = ttk.Button(self.content_frame, text="Save", command=self.saveEvent)
        self.save.grid(row=3, column=3)
        self.showMore = lambda y = self: EventView.showMore(y)
        self.showmore = ttk.Button(self.content_frame, text="Show more", command=self.showMore)
        self.showmore.grid(row=3, column=2)

    def showMore(self):
        self.showmore.destroy()
        self.showLess = lambda y = self: EventView.showLess(y) #This is wierd, but it works. Is the this lambda function overwriting the already defined method in the instantiated object?
        self.showless = ttk.Button(self.content_frame, text="Show less", command=self.showLess)
        self.showless.grid(row=3, column=2)
        self.moreDetailsFrame = ttk.Frame(self.dialog)
        self.moreDetailsFrame.pack(side=TKC.BOTTOM)
        self.locationLabel = ttk.Label(self.moreDetailsFrame, text="Location")
        self.locationLabel.grid()
        self.locationEntry = ttk.Entry(self.moreDetailsFrame)
        self.locationEntry.grid()
        self.invitees = ttk.Button(self.moreDetailsFrame, text = "Invitees")
        self.invitees.grid()
        self.begin_timeLabel = ttk.Label(self.moreDetailsFrame, text = "Begin Time")
        self.begin_timeLabel.grid()
        self.begin_time = ttk.Entry(self.moreDetailsFrame)
        self.begin_time.grid()
        self.end_timeLabel = ttk.Label(self.moreDetailsFrame, text = "End Time")
        self.end_timeLabel.grid()
        self.end_time = ttk.Entry(self.moreDetailsFrame)
        self.end_time.grid()
        self.second_save = ttk.Button(self.moreDetailsFrame, text="Save", command=self.saveEvent)
        self.second_save.grid()

    def showLess(self):
        self.second_save.destroy()
        self.showless.destroy()
        self.moreDetailsFrame.destroy()
        self.showmore = ttk.Button(self.content_frame, text="Show more", command=self.showMore)
        self.showmore.grid(row=3, column=2)

class Schedule:
    def __init__(self):
        #self.dialog = tkinter.Toplevel(root)
        #self.dialog.title("Event Details")
        self.window_frame = ttk.Frame(root)
        self.window_frame.pack()
        self.content_frame = tkinter.Frame(self.window_frame)
        self.content_frame.pack()

################### OBSERVER ##################################
class Subject:#Observer Class. http://code.activestate.com/recipes/131499-observer-pattern/
    def __init__(self):
        self._observers =[] #instantiate the Observer Object.
        #self._observers will hold a dictionary value

    def register(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    def deregister(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self, modifier):
        for observer in self._observers:
            if modifier == observer[1]:
                observer[0].update()
Observer = Subject() 


########################## CONTROLLER ######################
class CalendarController:
    def getUserInputFromCalendar(eventObject = None):
        array = {}
        array["date"] = eventObject.DATE.get()
        array["heading"] = eventObject.headingEntry.get()
        array["details"] = eventObject.detailsEntry.get('1.0','end') #The get method for the tkinter Text box is wierd, and has little documentation. see: http://www.tkdocs.com/tutorial/text.html
        try:
            array["location"] = eventObject.locationEntry.get()
            array["begin_time"] = eventObject.begin_time.get()
            array["end_time"] = eventObject.end_time.get()
        except:
            pass
        return array
    
    def getHeading(date):
        a= Model.getEventsHeadings(str(date))
        return a

    def getEventDetails(event_id):
        return Model.getEventsDetails(event_id)

    def deleteEvent(eventObject):
        event_id = eventObject.EVENTID.get()
        print(event_id+"!!!!!!!!!")
        date = eventObject.DATE.get()
        Model.deleteEvent(event_id)
        eventObject.dialog.destroy()
        Observer.notify(date)
        
    def saveEvent(eventObject):
        array = CalendarController.getUserInputFromCalendar(eventObject)
        eventObject.dialog.destroy()
        Model.addEvent(array)
        Observer.notify(array["date"])

    def modifyEvent(eventObject):
        array = CalendarController.getUserInputFromCalendar(eventObject)
        array["event_id"] = eventObject.EVENTID.get()
        eventObject.dialog.destroy()
        Model.modifyEvent(array)
        Observer.notify(array["date"])
    
class Controller:
    View()
    CalendarView()
    #Schedule()
    #GlobalMenu()

root.mainloop() #root must be called outside of the Controller Class, or else you can't call functions within the Controller class (03-01-2012)
